/*
* JBoss, by Red Hat.
* Copyright 2010, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.seam.forge.shell;
import static org.mvel2.DataConversion.addConversionHandler;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import jline.console.ConsoleReader;
import jline.console.completer.AggregateCompleter;
import jline.console.completer.Completer;
import jline.console.completer.FileNameCompleter;
import jline.console.history.MemoryHistory;
import org.fusesource.jansi.Ansi;
import org.jboss.seam.forge.project.Project;
import org.jboss.seam.forge.project.dependencies.Dependency;
import org.jboss.seam.forge.project.facets.JavaSourceFacet;
import org.jboss.seam.forge.project.services.ResourceFactory;
import org.jboss.seam.forge.resources.DirectoryResource;
import org.jboss.seam.forge.resources.FileResource;
import org.jboss.seam.forge.resources.Resource;
import org.jboss.seam.forge.resources.java.JavaResource;
import org.jboss.seam.forge.shell.command.PromptTypeConverter;
import org.jboss.seam.forge.shell.command.convert.BooleanConverter;
import org.jboss.seam.forge.shell.command.convert.DependencyIdConverter;
import org.jboss.seam.forge.shell.command.convert.FileConverter;
import org.jboss.seam.forge.shell.command.fshparser.FSHRuntime;
import org.jboss.seam.forge.shell.completer.CompletedCommandHolder;
import org.jboss.seam.forge.shell.completer.OptionAwareCompletionHandler;
import org.jboss.seam.forge.shell.completer.PluginCommandCompleter;
import org.jboss.seam.forge.shell.events.AcceptUserInput;
import org.jboss.seam.forge.shell.events.PostStartup;
import org.jboss.seam.forge.shell.events.PreShutdown;
import org.jboss.seam.forge.shell.events.Shutdown;
import org.jboss.seam.forge.shell.events.Startup;
import org.jboss.seam.forge.shell.exceptions.AbortedException;
import org.jboss.seam.forge.shell.exceptions.CommandExecutionException;
import org.jboss.seam.forge.shell.exceptions.CommandParserException;
import org.jboss.seam.forge.shell.exceptions.PluginExecutionException;
import org.jboss.seam.forge.shell.exceptions.ShellExecutionException;
import org.jboss.seam.forge.shell.plugins.builtin.Echo;
import org.jboss.seam.forge.shell.project.CurrentProject;
import org.jboss.seam.forge.shell.util.Files;
import org.jboss.seam.forge.shell.util.GeneralUtils;
import org.jboss.seam.forge.shell.util.JavaPathspecParser;
import org.jboss.seam.forge.shell.util.OSUtils;
import org.jboss.seam.forge.shell.util.ResourceUtil;
import org.jboss.weld.environment.se.bindings.Parameters;
import org.mvel2.ConversionHandler;
import org.mvel2.DataConversion;
import org.mvel2.util.StringAppender;
import sun.misc.Signal;
import sun.misc.SignalHandler;
/**
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*/
@ApplicationScoped
@SuppressWarnings("restriction")
public class ShellImpl implements Shell
{
private static final String PROP_PROMPT = "PROMPT";
private static final String PROP_PROMPT_NO_PROJ = "PROMPT_NOPROJ";
private static final String DEFAULT_PROMPT = "[\\c{green}$PROJECT_NAME\\c] \\c{white}\\W\\c \\c{green}\\$\\c ";
private static final String DEFAULT_PROMPT_NO_PROJ = "[\\c{red}no project\\c] \\c{white}\\W\\c \\c{red}\\$\\c ";
private static final String PROP_DEFAULT_PLUGIN_REPO = "DEFFAULT_PLUGIN_REPO";
private static final String DEFAULT_PLUGIN_REPO = "http://seamframework.org/service/File/148617";
private static final String PROP_VERBOSE = "VERBOSE";
public static final String FORGE_CONFIG_DIR = System.getProperty("user.home") + "/.forge/";
public static final String FORGE_COMMAND_HISTORY_FILE = "cmd_history";
public static final String FORGE_CONFIG_FILE = "config";
private final Map<String, Object> properties = new HashMap<String, Object>();
@Inject
@Parameters
private List<String> parameters;
@Inject
private Event<PostStartup> postStartup;
@Inject
private CurrentProject projectContext;
@Inject
private ResourceFactory resourceFactory;
private Resource<?> lastResource;
@Inject
private FSHRuntime fshRuntime;
@Inject
private PromptTypeConverter promptTypeConverter;
@Inject
private CompletedCommandHolder commandHolder;
private ConsoleReader reader;
private Completer completer;
private boolean pretend = false;
private boolean exitRequested = false;
private InputStream inputStream;
private Writer outputWriter;
private OutputStream historyOutstream;
private final boolean colorEnabled = Boolean.getBoolean("seam.forge.shell.colorEnabled");
private final ConversionHandler resourceConversionHandler = new ConversionHandler()
{
@Override
@SuppressWarnings("rawtypes")
public Resource[] convertFrom(final Object obl)
{
return GeneralUtils.parseSystemPathspec(resourceFactory, lastResource, getCurrentResource(),
obl instanceof String[] ? (String[]) obl : new String[] { obl.toString() });
}
@SuppressWarnings("rawtypes")
@Override
public boolean canConvertFrom(final Class aClass)
{
return true;
}
};
private final ConversionHandler javaResourceConversionHandler = new ConversionHandler()
{
@Override
public JavaResource[] convertFrom(final Object obj)
{
if (getCurrentProject().hasFacet(JavaSourceFacet.class))
{
String[] strings = obj instanceof String[] ? (String[]) obj : new String[] { obj.toString() };
List<Resource<?>> resources = new ArrayList<Resource<?>>();
for (String string : strings)
{
resources.addAll(new JavaPathspecParser(getCurrentProject().getFacet(JavaSourceFacet.class),
string).resolve());
}
List<JavaResource> filtered = new ArrayList<JavaResource>();
for (Resource<?> resource : resources)
{
if (resource instanceof JavaResource)
{
filtered.add((JavaResource) resource);
}
}
JavaResource[] result = new JavaResource[filtered.size()];
result = filtered.toArray(result);
return result;
}
else
return null;
}
@SuppressWarnings("rawtypes")
@Override
public boolean canConvertFrom(final Class aClass)
{
return true;
}
};
private boolean exitOnNextSignal = false;
void init(@Observes final Startup event, final PluginCommandCompleter pluginCompleter) throws Exception
{
BooleanConverter booleanConverter = new BooleanConverter();
addConversionHandler(boolean.class, booleanConverter);
addConversionHandler(Boolean.class, booleanConverter);
addConversionHandler(File.class, new FileConverter());
addConversionHandler(Dependency.class, new DependencyIdConverter());
addConversionHandler(JavaResource[].class, javaResourceConversionHandler);
addConversionHandler(JavaResource.class, new ConversionHandler()
{
@Override
public Object convertFrom(final Object obj)
{
JavaResource[] res = (JavaResource[]) javaResourceConversionHandler.convertFrom(obj);
if (res.length > 1)
{
throw new RuntimeException("ambiguous paths");
}
else if (res.length == 0)
{
if (getCurrentProject().hasFacet(JavaSourceFacet.class))
{
JavaSourceFacet java = getCurrentProject().getFacet(JavaSourceFacet.class);
try
{
JavaResource resource = java.getJavaResource(obj.toString());
return resource;
}
catch (FileNotFoundException e)
{
throw new RuntimeException(e);
}
}
return null;
}
else
{
return res[0];
}
}
@Override
@SuppressWarnings("rawtypes")
public boolean canConvertFrom(final Class type)
{
return javaResourceConversionHandler.canConvertFrom(type);
}
});
addConversionHandler(Resource[].class, resourceConversionHandler);
addConversionHandler(Resource.class, new ConversionHandler()
{
@Override
public Object convertFrom(final Object o)
{
Resource<?>[] res = (Resource<?>[]) resourceConversionHandler.convertFrom(o);
if (res.length > 1)
{
throw new RuntimeException("ambiguous paths");
}
else if (res.length == 0)
{
return ResourceUtil.parsePathspec(resourceFactory, getCurrentResource(), o.toString()).get(0);
}
else
{
return res[0];
}
}
@Override
@SuppressWarnings("rawtypes")
public boolean canConvertFrom(final Class aClass)
{
return resourceConversionHandler.canConvertFrom(aClass);
}
});
projectContext.setCurrentResource(resourceFactory.getResourceFrom(event.getWorkingDirectory()));
properties.put("CWD", getCurrentDirectory().getFullyQualifiedName());
initStreams();
initCompleters(pluginCompleter);
initParameters();
initSignalHandlers();
if (event.isRestart())
{
// suppress the MOTD if this is a restart.
properties.put("NO_MOTD", true);
}
else
{
properties.put("NO_MOTD", false);
}
properties.put("OS_NAME", OSUtils.getOsName());
properties.put("FORGE_CONFIG_DIR", FORGE_CONFIG_DIR);
properties.put(PROP_PROMPT, "> ");
properties.put(PROP_PROMPT_NO_PROJ, "> ");
loadConfig();
postStartup.fire(new PostStartup());
}
private static void initSignalHandlers()
{
try
{
// check to see if we have something to work with.
Class.forName("sun.misc.SignalHandler");
SignalHandler signalHandler = new SignalHandler()
{
@Override
public void handle(final Signal signal)
{
// TODO implement smart shutdown (if they keep pressing CTRL-C)
}
};
Signal.handle(new Signal("INT"), signalHandler);
}
catch (ClassNotFoundException e)
{
// signal trapping not supported. Oh well, switch to a Sun-based JVM, loser!
}
}
private void loadConfig()
{
File configDir = new File(FORGE_CONFIG_DIR);
if (!configDir.exists())
{
if (!configDir.mkdirs())
{
System.err.println("could not create config directory: " + configDir.getAbsolutePath());
return;
}
}
File historyFile = new File(configDir.getPath() + "/" + FORGE_COMMAND_HISTORY_FILE);
try
{
if (!historyFile.exists())
{
if (!historyFile.createNewFile())
{
System.err.println("could not create config file: " + historyFile.getAbsolutePath());
}
}
}
catch (IOException e)
{
throw new RuntimeException("could not create config file: " + historyFile.getAbsolutePath());
}
MemoryHistory history = new MemoryHistory();
try
{
StringAppender buf = new StringAppender();
InputStream instream = new BufferedInputStream(new FileInputStream(historyFile));
byte[] b = new byte[25];
int read;
while ((read = instream.read(b)) != -1)
{
for (int i = 0; i < read; i++)
{
if (b[i] == '\n')
{
history.add(buf.toString());
buf.reset();
}
else
{
buf.append(b[i]);
}
}
}
instream.close();
reader.setHistory(history);
}
catch (IOException e)
{
throw new RuntimeException("error loading file: " + historyFile.getAbsolutePath());
}
File configFile = new File(configDir.getPath() + "/" + FORGE_CONFIG_FILE);
if (!configFile.exists())
{
try
{
/**
* Create a default config file.
*/
configFile.createNewFile();
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(configFile));
String defaultConfig = getDefaultConfig();
for (int i = 0; i < defaultConfig.length(); i++)
{
outputStream.write(defaultConfig.charAt(i));
}
outputStream.flush();
outputStream.close();
}
catch (IOException e)
{
e.printStackTrace();
throw new RuntimeException("error loading file: " + historyFile.getAbsolutePath());
}
}
try
{
/**
* Load the config file script.
*/
execute(configFile);
}
catch (IOException e)
{
e.printStackTrace();
throw new RuntimeException("error loading file: " + historyFile.getAbsolutePath());
}
try
{
historyOutstream = new BufferedOutputStream(new FileOutputStream(historyFile, true));
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
try
{
historyOutstream.flush();
historyOutstream.close();
}
catch (Exception e)
{
}
}
});
}
catch (IOException e)
{
}
}
private void writeToHistory(final String command)
{
try
{
for (int i = 0; i < command.length(); i++)
{
historyOutstream.write(command.charAt(i));
}
historyOutstream.write('\n');
}
catch (IOException e)
{
}
}
private void initCompleters(final PluginCommandCompleter pluginCompleter)
{
List<Completer> completers = new ArrayList<Completer>();
completers.add(pluginCompleter);
completer = new AggregateCompleter(completers);
this.reader.addCompleter(completer);
this.reader.setCompletionHandler(new OptionAwareCompletionHandler(commandHolder, this));
}
private void initStreams() throws IOException
{
if (inputStream == null)
{
inputStream = System.in;
}
if (outputWriter == null)
{
outputWriter = new PrintWriter(System.out);
}
this.reader = new ConsoleReader(inputStream, outputWriter);
this.reader.setHistoryEnabled(true);
this.reader.setBellEnabled(false);
}
private void initParameters()
{
properties.put(PROP_VERBOSE, String.valueOf(parameters.contains("--verbose")));
if (parameters.contains("--pretend"))
{
pretend = true;
}
if ((parameters != null) && !parameters.isEmpty())
{
// this is where we will initialize other parameters... e.g. accepting
// a path
}
}
private String getDefaultConfig()
{
return "@/* Automatically generated config file */;\n"
+
"if (!$NO_MOTD) { "
+
" echo \" ____ _____ \";\n"
+
" echo \" / ___| ___ __ _ _ __ ___ | ___|__ _ __ __ _ ___ \";\n"
+
" echo \" \\\\___ \\\\ / _ \\\\/ _` | '_ ` _ \\\\ | |_ / _ \\\\| '__/ _` |/ _ \\\\ \\c{yellow}\\\\\\\\\\c\";\n"
+
" echo \" ___) | __/ (_| | | | | | | | _| (_) | | | (_| | __/ \\c{yellow}//\\c\";\n" +
" echo \" |____/ \\\\___|\\\\__,_|_| |_| |_| |_| \\\\___/|_| \\\\__, |\\\\___| \";\n" +
" echo \" |___/ \";\n\n" +
"}\n" +
"\n" +
"if ($OS_NAME.startsWith(\"Windows\")) {\n" +
" echo \" Windows? Really? Okay...\\n\"\n" +
"}\n" +
"\n" +
"set " + PROP_PROMPT + " \"" + DEFAULT_PROMPT + "\";\n" +
"set " + PROP_PROMPT_NO_PROJ + " \"" + DEFAULT_PROMPT_NO_PROJ + "\";\n" +
"set " + PROP_DEFAULT_PLUGIN_REPO + " \"" + DEFAULT_PLUGIN_REPO + "\"\n";
}
void teardown(@Observes final Shutdown shutdown, final Event<PreShutdown> preShutdown)
{
preShutdown.fire(new PreShutdown(shutdown.getStatus()));
exitRequested = true;
}
void doShell(@Observes final AcceptUserInput event)
{
String line;
reader.setPrompt(getPrompt());
while (!exitRequested)
{
try
{
line = readLineShell();
if (line != null)
{
if (!"".equals(line.trim()))
{
writeToHistory(line);
execute(line);
}
reader.setPrompt(getPrompt());
}
}
catch (Exception e)
{
handleException(e);
}
}
println();
}
private void handleException(final Exception original)
{
try
{
// unwrap any aborted exceptions
Throwable cause = original;
while (cause != null)
{
if (cause instanceof AbortedException)
throw (AbortedException) cause;
cause = cause.getCause();
}
throw original;
}
catch (AbortedException e)
{
ShellMessages.info(this, "Aborted.");
if (isVerbose())
{
e.printStackTrace();
}
}
catch (CommandExecutionException e)
{
ShellMessages.error(this, formatSourcedError(e.getCommand()) + e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (CommandParserException e)
{
ShellMessages.error(this, "[" + formatSourcedError(e.getCommand()) + "] " + e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (PluginExecutionException e)
{
ShellMessages.error(this, "[" + formatSourcedError(e.getPlugin()) + "] " + e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (ShellExecutionException e)
{
ShellMessages.error(this, e.getMessage());
if (isVerbose())
{
e.printStackTrace();
}
}
catch (Exception e)
{
if (!isVerbose())
{
ShellMessages.error(this, "Exception encountered: " + e.getMessage()
+ " (type \"set VERBOSE true\" to enable stack traces)");
}
else
{
ShellMessages.error(this, "Exception encountered: (type \"set VERBOSE false\" to disable stack traces)");
e.printStackTrace();
}
}
}
private String formatSourcedError(final Object obj)
{
return (obj == null ? "" : ("[" + obj.toString() + "] "));
}
@Override
public String readLine() throws IOException
{
String line = reader.readLine();
if (line == null)
{
reader.println();
reader.flush();
throw new AbortedException();
}
exitOnNextSignal = false;
return line;
}
private String readLineShell() throws IOException
{
String line = reader.readLine();
if (line == null)
{
if (this.exitOnNextSignal == false)
{
println();
println("(Press CTRL-D again or type 'exit' to quit.)");
this.exitOnNextSignal = true;
}
else
{
print("exit");
this.exitRequested = true;
}
reader.flush();
}
else
{
exitOnNextSignal = false;
}
return line;
}
@Override
public int scan()
{
try
{
return reader.readVirtualKey();
}
catch (IOException e)
{
return -1;
}
}
@Override
public void clearLine()
{
print(new Ansi().eraseLine(Ansi.Erase.ALL).toString());
}
@Override
public void cursorLeft(final int x)
{
print(new Ansi().cursorLeft(x).toString());
}
@Override
public void execute(final String line)
{
try
{
fshRuntime.run(line);
}
catch (Exception e)
{
handleException(e);
}
}
@Override
public void execute(final File file) throws IOException
{
StringBuilder buf = new StringBuilder();
InputStream instream = new BufferedInputStream(new FileInputStream(file));
try
{
byte[] b = new byte[25];
int read;
while ((read = instream.read(b)) != -1)
{
for (int i = 0; i < read; i++)
{
buf.append((char) b[i]);
}
}
instream.close();
execute(buf.toString());
}
finally
{
instream.close();
}
}
@Override
public void execute(final File file, final String... args) throws IOException
{
StringBuilder buf = new StringBuilder();
String funcName = file.getName().replaceAll("\\.", "_") + "_" + String.valueOf(hashCode()).replaceAll("\\-", "M");
buf.append("def ").append(funcName).append('(');
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
buf.append("_").append(String.valueOf(i));
if (i + 1 < args.length)
{
buf.append(", ");
}
}
}
buf.append(") {\n");
if (args != null)
{
buf.append("@_vararg = new String[").append(args.length).append("];\n");
for (int i = 0; i < args.length; i++)
{
buf.append("@_vararg[").append(String.valueOf(i)).append("] = ")
.append("_").append(String.valueOf(i)).append(";\n");
}
}
InputStream instream = new BufferedInputStream(new FileInputStream(file));
try
{
byte[] b = new byte[25];
int read;
while ((read = instream.read(b)) != -1)
{
for (int i = 0; i < read; i++)
{
buf.append((char) b[i]);
}
}
buf.append("\n}; \n@").append(funcName).append('(');
if (args != null)
{
for (int i = 0; i < args.length; i++)
{
buf.append("\"").append(args[i].replaceAll("\\\"", "\\\\\\\"")).append("\"");
if (i + 1 < args.length)
{
buf.append(", ");
}
}
}
buf.append(");\n");
// System.out.println("\nexec:" + buf.toString());
execute(buf.toString());
}
finally
{
properties.remove(funcName);
instream.close();
}
}
/*
* Shell Print Methods
*/
@Override
public void printlnVerbose(final String line)
{
if (isVerbose())
{
System.out.println(line);
}
}
@Override
public void print(final String output)
{
System.out.print(output);
}
@Override
public void println(final String output)
{
System.out.println(output);
}
@Override
public void println()
{
System.out.println();
}
@Override
public void print(final ShellColor color, final String output)
{
print(renderColor(color, output));
}
@Override
public void println(final ShellColor color, final String output)
{
println(renderColor(color, output));
}
@Override
public void printlnVerbose(final ShellColor color, final String output)
{
printlnVerbose(renderColor(color, output));
}
@Override
public String renderColor(final ShellColor color, final String output)
{
if (!colorEnabled)
{
return output;
}
Ansi ansi = new Ansi();
switch (color)
{
case BLACK:
ansi.fg(Ansi.Color.BLACK);
break;
case BLUE:
ansi.fg(Ansi.Color.BLUE);
break;
case CYAN:
ansi.fg(Ansi.Color.CYAN);
break;
case GREEN:
ansi.fg(Ansi.Color.GREEN);
break;
case MAGENTA:
ansi.fg(Ansi.Color.MAGENTA);
break;
case RED:
ansi.fg(Ansi.Color.RED);
break;
case WHITE:
ansi.fg(Ansi.Color.WHITE);
break;
case YELLOW:
ansi.fg(Ansi.Color.YELLOW);
break;
case BOLD:
ansi.a(Ansi.Attribute.INTENSITY_BOLD);
break;
default:
ansi.fg(Ansi.Color.WHITE);
}
return ansi.render(output).reset().toString();
}
@Override
public void write(final byte b)
{
System.out.print((char) b);
}
@Override
public void clear()
{
print(new Ansi().cursor(0, 0).eraseScreen().toString());
}
@Override
public boolean isVerbose()
{
Object s = properties.get(PROP_VERBOSE);
return (s != null) && "true".equals(s);
}
@Override
public void setVerbose(final boolean verbose)
{
properties.put(PROP_VERBOSE, String.valueOf(verbose));
}
@Override
public boolean isPretend()
{
return pretend;
}
@Override
public void setInputStream(final InputStream is) throws IOException
{
this.inputStream = is;
initStreams();
}
@Override
public void setOutputWriter(final Writer os) throws IOException
{
this.outputWriter = os;
initStreams();
}
@Override
public void setProperty(final String name, final Object value)
{
properties.put(name, value);
}
@Override
public Object getProperty(final String name)
{
return properties.get(name);
}
@Override
public Map<String, Object> getProperties()
{
return properties;
}
@Override
public void setDefaultPrompt()
{
setPrompt("");
}
@Override
public void setPrompt(final String prompt)
{
setProperty(PROP_PROMPT, prompt);
}
@Override
public String getPrompt()
{
if (projectContext.getCurrent() != null)
{
return Echo.echo(this, Echo.promptExpressionParser(this, (String) properties.get(PROP_PROMPT)));
}
else
{
return Echo.echo(this, Echo.promptExpressionParser(this, (String) properties.get(PROP_PROMPT_NO_PROJ)));
}
}
@Override
public DirectoryResource getCurrentDirectory()
{
Resource<?> r = getCurrentResource();
return ResourceUtil.getContextDirectory(r);
}
@Override
public Resource<?> getCurrentResource()
{
Resource<?> result = this.projectContext.getCurrentResource();
if (result == null)
{
result = this.resourceFactory.getResourceFrom(Files.getWorkingDirectory());
properties.put("CWD", result.getFullyQualifiedName());
}
return result;
}
@Override
@SuppressWarnings("unchecked")
public Class<? extends Resource<?>> getCurrentResourceScope()
{
return (Class<? extends Resource<?>>) getCurrentResource().getClass();
}
@Override
public void setCurrentResource(final Resource<?> resource)
{
lastResource = getCurrentResource();
projectContext.setCurrentResource(resource);
properties.put("CWD", resource.getFullyQualifiedName());
}
@Override
public Project getCurrentProject()
{
return this.projectContext.getCurrent();
}
/*
* Shell Prompts
*/
@Override
public String prompt()
{
return prompt("");
}
@Override
public String promptAndSwallowCR()
{
int c;
StringBuilder buf = new StringBuilder();
while (((c = scan()) != '\n') && (c != '\r'))
{
if (c == 127)
{
if (buf.length() > 0)
{
buf.deleteCharAt(buf.length() - 1);
cursorLeft(1);
print(" ");
cursorLeft(1);
}
continue;
}
write((byte) c);
buf.append((char) c);
}
return buf.toString();
}
@Override
public String prompt(final String message)
{
return promptWithCompleter(message, null);
}
private String promptWithCompleter(String message, final Completer tempCompleter)
{
if (!message.isEmpty() && message.matches("^.*\\S$"))
{
message = message + " ";
}
try
{
reader.removeCompleter(this.completer);
if (tempCompleter != null)
{
reader.addCompleter(tempCompleter);
}
reader.setHistoryEnabled(false);
reader.setPrompt(message);
String line = readLine();
return line;
}
catch (IOException e)
{
throw new IllegalStateException("Shell input stream failure", e);
}
finally
{
if (tempCompleter != null)
{
reader.removeCompleter(tempCompleter);
}
reader.addCompleter(this.completer);
reader.setHistoryEnabled(true);
reader.setPrompt("");
}
}
@Override
public String promptRegex(final String message, final String regex)
{
String input;
do
{
input = prompt(message);
}
while (!input.matches(regex));
return input;
}
@Override
public String promptRegex(final String message, final String pattern, final String defaultIfEmpty)
{
if (!defaultIfEmpty.matches(pattern))
{
throw new IllegalArgumentException("Default value [" + defaultIfEmpty + "] does not match required pattern ["
+ pattern + "]");
}
String input;
do
{
input = prompt(message + " [" + defaultIfEmpty + "]");
if ("".equals(input.trim()))
{
input = defaultIfEmpty;
}
}
while (!input.matches(pattern));
return input;
}
@Override
@SuppressWarnings("unchecked")
public <T> T prompt(final String message, final Class<T> clazz)
{
Object result;
Object input;
do
{
input = prompt(message);
try
{
result = DataConversion.convert(input, clazz);
}
catch (Exception e)
{
result = InvalidInput.INSTANCE;
}
}
while ((result instanceof InvalidInput));
return (T) result;
}
@Override
@SuppressWarnings("unchecked")
public <T> T prompt(final String message, final Class<T> clazz, final T defaultIfEmpty)
{
Object result;
String input;
do
{
input = prompt(message);
if ((input == null) || "".equals(input.trim()))
{
result = defaultIfEmpty;
}
else
{
input = input.trim();
try
{
result = DataConversion.convert(input, clazz);
}
catch (Exception e)
{
result = InvalidInput.INSTANCE;
}
}
}
while ((result instanceof InvalidInput));
return (T) result;
}
@Override
public boolean promptBoolean(final String message)
{
return promptBoolean(message, true);
}
@Override
public boolean promptBoolean(final String message, final boolean defaultIfEmpty)
{
String query = " [Y/n] ";
if (!defaultIfEmpty)
{
query = " [y/N] ";
}
return prompt(message + query, Boolean.class, defaultIfEmpty);
}
@Override
public int promptChoice(final String message, final Object... options)
{
return promptChoice(message, Arrays.asList(options));
}
@Override
public int promptChoice(final String message, final List<?> options)
{
if ((options == null) || options.isEmpty())
{
throw new IllegalArgumentException(
"promptChoice() Cannot ask user to select from a list of nothing. Ensure you have values in your options list.");
}
int count = 1;
println(message);
Object result = InvalidInput.INSTANCE;
while (result instanceof InvalidInput)
{
println();
for (Object entry : options)
{
println(" " + count + " - [" + entry + "]");
count++;
}
println();
int input = prompt("Choose an option by typing the number of the selection: ", Integer.class) - 1;
if (input < options.size())
{
return input;
}
else
{
println("Invalid selection, please try again.");
}
}
return -1;
}
@Override
public <T> T promptChoiceTyped(final String message, final T... options)
{
return promptChoiceTyped(message, Arrays.asList(options));
}
@Override
@SuppressWarnings("unchecked")
public <T> T promptChoiceTyped(final String message, final List<T> options)
{
if ((options == null) || options.isEmpty())
{
throw new IllegalArgumentException(
"promptChoice() Cannot ask user to select from a list of nothing. Ensure you have values in your options list.");
}
if (options.size() == 1)
{
return options.get(0);
}
int count = 1;
println(message);
Object result = InvalidInput.INSTANCE;
while (result instanceof InvalidInput)
{
println();
for (T entry : options)
{
println(" " + count + " - [" + entry + "]");
count++;
}
println();
int input = prompt("Choose an option by typing the number of the selection: ", Integer.class) - 1;
if ((input >= 0) && (input < options.size()))
{
result = options.get(input);
}
else
{
println("Invalid selection, please try again.");
}
}
return (T) result;
}
@Override
public int getHeight()
{
return reader.getTerminal().getHeight();
}
@Override
public int getWidth()
{
return reader.getTerminal().getWidth();
}
public String escapeCode(final int code, final String value)
{
return new Ansi().a(value).fg(Ansi.Color.BLUE).toString();
}
@Override
@SuppressWarnings("unchecked")
public <T> T promptChoice(final String message, final Map<String, T> options)
{
int count = 1;
println(message);
List<Entry<String, T>> entries = new ArrayList<Map.Entry<String, T>>();
entries.addAll(options.entrySet());
Object result = InvalidInput.INSTANCE;
while (result instanceof InvalidInput)
{
println();
for (Entry<String, T> entry : entries)
{
println(" " + count + " - [" + entry.getKey() + "]");
count++;
}
println();
String input = prompt("Choose an option by typing the name or number of the selection: ");
if (options.containsKey(input))
{
result = options.get(input);
}
}
return (T) result;
}
@Override
public String promptCommon(final String message, final PromptType type)
{
String result = promptRegex(message, type.getPattern());
result = promptTypeConverter.convert(type, result);
return result;
}
@Override
public String promptCommon(final String message, final PromptType type, final String defaultIfEmpty)
{
String result = promptRegex(message, type.getPattern(), defaultIfEmpty);
result = promptTypeConverter.convert(type, result);
return result;
}
@Override
public FileResource<?> promptFile(final String message)
{
String path = "";
while ((path == null) || path.trim().isEmpty())
{
path = promptWithCompleter(message, new FileNameCompleter());
}
path = Files.canonicalize(path);
Resource<File> resource = resourceFactory.getResourceFrom(new File(path));
if (resource instanceof FileResource)
{
return (FileResource<?>) resource;
}
return null;
}
@Override
public FileResource<?> promptFile(final String message, final FileResource<?> defaultIfEmpty)
{
FileResource<?> result = defaultIfEmpty;
String path = promptWithCompleter(message, new FileNameCompleter());
if (!"".equals(path) && (path != null) && !path.trim().isEmpty())
{
path = Files.canonicalize(path);
Resource<File> resource = resourceFactory.getResourceFrom(new File(path));
if (resource instanceof FileResource)
{
result = (FileResource<?>) resource;
}
else
{
result = null;
}
}
return result;
}
}